import { expectType } from 'tsd'
import { Flatten } from './1.属性修饰进阶'
// 基于键值类型的 Pick 与 Omit；
// 子结构的互斥处理。

//# 基于键值类型的 Pick 与 Omit；
// 只不过，现在我们无法预先确定要拆分的属性了，而是需要基于期望的类型去拿到所有此类型的属性名，如想 Pick 出所有函数类型的值，那就要先拿到所有的函数类型属性名。先来一个 FunctionKeys 工具类型：

type FuncStruct = (...args: any[]) => any

type FunctionKeys<T extends object> = {
  [K in keyof T]: T[K] extends FuncStruct ? K : never
}[keyof T]

//{}[keyof T] 这个写法我们是第一次见，但我们可以拆开来看，先看看前面的 { [K in keyof T]: T[K] extends FuncStruct ? K : never; } 部分，为何在条件类型成立时它返回了键名 K，而非索引类型查询 T[K] ？
type Tmp<T extends object> = {
  [K in keyof T]: T[K] extends FuncStruct ? K : never
}

type Res = Tmp<{
  foo: () => void
  bar: () => number
  baz: number
}>

// 在 Res（等价于 ResEqual）中
type ResEqual = {
  foo: 'foo'
  bar: 'bar'
  baz: never
}
//我们获得了一个属性名-属性名字面量类型的结构，对于非函数类型的属性，其值为 never。然后，我们加上 [keyof T] 这一索引类型查询 + keyof 操作符的组合：
type WhatWillWeGet = Res[keyof Res] // "foo" | "bar"
//我们神奇地获得了所有函数类型的属性名！这又是如何实现的呢？其实就是我们此前学习过的，当索引类型查询中使用了一个联合类型时，它会使用类似分布式条件类型的方式，将这个联合类型的成员依次进行访问，然后再最终组合起来，上面的例子可以这么简化：
type WhatWillWeGetEqual1 = Res['foo' | 'bar' | 'baz']

type WhatWillWeGetEqual2 = Res['foo'] | Res['bar'] | Res['baz']

type WhatWillWeGetEqual3 = 'foo' | 'bar' | never // $ 注意这个的返回

// 通过这一方式，我们就能够获取到符合预期类型的属性名了。如果希望抽象“基于键值类型查找属性”名这么个逻辑，我们就需要对 FunctionKeys 的逻辑进行封装，即将预期类型也作为泛型参数，由外部传入：
type FunctionStruct = (...args: any[]) => any
type ExpectedPropKeys<T extends object, ValueType> = {
  [Key in keyof T]-?: T[Key] extends ValueType ? Key : never
}[keyof T]

// 拿到了以 函数结构类型的属性值 的属性名
type FunctionKeys2<T extends object> = ExpectedPropKeys<T, FunctionStruct>

expectType<
  FunctionKeys2<{
    foo: () => void
    bar: () => number
    baz: number
  }>
>('foo')
type testExpectType = FunctionKeys2<{
  foo: () => void
  bar: () => number
  baz: number
}>
expectType<testExpectType>('bar')

// expectType<
//   FunctionKeys<{
//     foo: () => void
//     bar: () => number
//     baz: number
//   }>
//   // 报错，因为 baz 不是函数类型属性
// >('baz')

//注意，为了避免可选属性对条件类型语句造成干扰，这里我们使用 -? 移除了所有可选标记。既然我们现在可以拿到对应类型的属性名，那么把这些属性交给 Pick，不就可以得到由这些属性组成的子结构了？
export type PickByValueType<T extends object, ValueType> = Pick<T, ExpectedPropKeys<T, ValueType>>

type PickByValueType_res1 = PickByValueType<{ name: string; age: number; isHeight: boolean }, string>

type PickByValueType_res2 = PickByValueType<
  {
    name: string
    age: number
    isHeight: boolean
  },
  string | number
>
expectType<PickByValueType<{ foo: string; bar: number }, string>>({
  foo: 'linbudu'
})

expectType<PickByValueType<{ foo: string; bar: number; baz: boolean }, string | number>>({
  foo: 'linbudu',
  bar: 599
})
// # OmitByValueType 也是类似的，我们只需要一个和 ExpectedPropKeys 作用相反的工具类型即可，比如来个 FilteredPropKeys，只需要调换条件类型语句结果的两端即可：

// 过滤出 T的属性值类型 不符合ValueType结构类型 的T的属性名
type FilteredPropKeys<T extends object, ValueType> = {
  [Key in keyof T]-?: T[Key] extends ValueType ? never : Key
}[keyof T]

export type OmitByValueType<T extends object, ValueType> = Pick<T, FilteredPropKeys<T, ValueType>>

type OmitByValueType_res1 = OmitByValueType<
  {
    name: string
    age: number
    isHeight: boolean
  },
  string | number
> //  { isHeight: boolean; }

expectType<OmitByValueType<{ foo: string; bar: number }, string>>({
  bar: 599
})

expectType<OmitByValueType<{ foo: string; bar: number; baz: boolean }, string | number>>({
  baz: true
})
//或者，如果你想把 ExpectedPropKeys 和 FilteredPropKeys 合并在一起，其实也很简单，只是需要引入第三个泛型参数来控制返回结果
type Conditional<Value, Condition, Resolved, Rejected> = Value extends Condition ? Resolved : Rejected

export type ValueTypeFilter<T extends object, ValueType, Positive extends boolean> = {
  [Key in keyof T]-?: T[Key] extends ValueType ? Conditional<Positive, true, Key, never> : Conditional<Positive, true, never, Key>
}[keyof T]

export type PickByValueType1<T extends object, ValueType> = Pick<T, ValueTypeFilter<T, ValueType, true>>

export type OmitByValueType1<T extends object, ValueType> = Pick<T, ValueTypeFilter<T, ValueType, false>>
// ---- 测试
type PickByValueType1_res1 = PickByValueType1<
  {
    name: string
    age: number
    isHeight: boolean
  },
  string | number
> //  { name: string;age: number;}

type OmitByValueType1_res1 = OmitByValueType1<
  {
    name: string
    age: number
    isHeight: boolean
  },
  string | number
> //  { isHeight: boolean; }

//即在联合类型的情况下，1 | 2 extends 1 | 2 | 3（通过泛型参数传入） 会被视为是合法的，这是由于分布式条件类型的存在。而有时我们希望对联合类型的比较是全等的比较，还记得我们说怎么禁用分布式条件类型吗？让它不满足裸类型参数这一条即可：

type Wrapped<T> = [T] extends [boolean] ? 'Y' : 'N'
type test_wrapped = Wrapped<true>

type StrictConditional<Value, Condition, Resolved, Rejected> = [Value] extends [Condition] ? Resolved : Rejected

type Res1 = StrictConditional<1 | 2, 1 | 2 | 3, true, false> // true
//当条件不再是一个简单的单体类型，而是一个联合类型时，我们使用数组的方式就产生问题了。因为 Array<1 | 2> extends Array<1 | 2 | 3> 就是合法的，第一个数组中的可能元素类型均被第二个数组的元素类型包含了，无论如何都是其子类型。

type StrictConditional1<A, B, Resolved, Rejected, Fallback = never> = [A] extends [B] ? ([B] extends [A] ? Resolved : Rejected) : Fallback

type StrictConditional_Res1 = StrictConditional1<1 | 2, 1 | 2 | 3, true, false> // false
type StrictConditional_Res2 = StrictConditional1<1 | 2 | 3, 1 | 2, true, false, false> // false
type StrictConditional_Res3 = StrictConditional1<1 | 2, 1 | 2, true, false> // true
type StrictConditional_Res4 = StrictConditional1<Array<1 | 2 | 3>, Array<1 | 2 | 3>, true, false> // true

//应用到 TypeFilter 中：
export type StrictValueTypeFilter<T extends object, ValueType, Positive extends boolean = true> = {
  [Key in keyof T]-?: StrictConditional1<
    ValueType,
    T[Key],
    // 为了避免嵌套太多工具类型，这里就不使用 Conditional 了
    Positive extends true ? Key : never,
    Positive extends true ? never : Key,
    Positive extends true ? never : Key
  >
}[keyof T]

export type StrictPickByValueType<T extends object, ValueType> = Pick<T, StrictValueTypeFilter<T, ValueType>>

type test111 = StrictPickByValueType<{ foo: 1; bar: 1 | 2; baz: 1 | 2 | 3 }, 1 | 2>

export type StrictOmitByValueType<T extends object, ValueType> = Pick<T, StrictValueTypeFilter<T, ValueType, false>>

type test222 = StrictOmitByValueType<{ foo: 1; bar: 1 | 2; baz: 1 | 2 | 3 }, 1 | 2>
expectType<StrictOmitByValueType<{ foo: 1; bar: 1 | 2; baz: 1 | 2 | 3 }, 1 | 2>>({
  foo: 1,
  baz: 3
})

//接下来是基于结构的互斥工具类型。想象这样一个场景，假设我们有一个用于描述用户信息的对象结构，除了共有的一些基础结构以外，VIP 用户和普通用户、游客这三种类型的用户各自拥有一些独特的字段，如 vipExpires 代表 VIP 过期时间，仅属于 VIP 用户，promotionUsed 代表已领取过体验券，属于普通用户，而 refererType 代表跳转来源，属于游客。先来看看如何声明一个接口，它要么拥有 vipExpires，要么拥有 promotionUsed 字段，而不能同时拥有这两个字段。你可能会首先想到使用联合类型？

interface VIP {
  vipExpires: number
}
interface CommonUser {
  promotionUsed: boolean
}
type User = VIP | CommonUser
//很遗憾，这种方式并不会约束“不能同时拥有”这个条件：
const user1: User = {
  vipExpires: 599,
  promotionUsed: false
}
//为了表示不能同时拥有，实际上我们应该使用 never 类型来标记一个属性。这里我们直接看完整的实现：
export type Without<T, U> = {
  [P in Exclude<keyof T, keyof U>]?: never
}

export type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T)

type XORUser = XOR<VIP, CommonUser>
expectType<XORUser>({
  vipExpires: 0
})

type xor_left = Flatten<Without<VIP, CommonUser> & CommonUser>
type xor_right = Flatten<Without<CommonUser, VIP> & VIP>

type xor = xor_left | xor_right

expectType<xor>({
  promotionUsed: false
  // vipExpires: 0
})

expectType<XORUser>({
  promotionUsed: false
})

// 报错，至少需要一个
// @ts-expect-error
expectType<XORUser>({})

// 报错，不允许同时拥有
// @ts-expect-error
expectType<XORUser>({
  promotionUsed: false,
  vipExpires: 0
})
//对 Without 做进一步展开可以看到，它其实就是将声明了一个不变的原属性+为 never 的其他属性的接口：

// {
//    vipExpires?: never;
// }
type Temp_1 = Flatten<Without<VIP, CommonUser>>
type Temp_11 = Flatten<Temp_1 & CommonUser>
type Temp_2 = Flatten<Without<CommonUser, VIP>>
type Temp_22 = Flatten<Temp_2 & VIP>

type res_33 = Temp_11 | Temp_22
const res_test_1: res_33 = {
  vipExpires: 1
  // promotionUsed: false
}

interface Visitor {
  refererType: number
}
// 联合类型会自动合并重复的部分
type XORUser2 = XOR<VIP, XOR<CommonUser, Visitor>>

// -- name和age互斥
type test_obj1 = {
  name?: undefined
  age: number
}
type test_obj2 = {
  name: string
  age?: undefined
  name2: string
}
type test_obj3 = test_obj1 | test_obj2
expectType<test_obj3>({
  name: 'lys',
  name2: 'lys'
  // name3: 'sdf'
})

//我们还可以使用互斥类型实现绑定效果，即要么同时拥有 A、B 属性，要么一个属性都没有：
type XORStruct = XOR<
  {},
  {
    foo: string
    bar: number
  }
>
// -- 拆解
type Temp_3 = Flatten<
  Without<
    {},
    {
      foo: string
      bar: number
    }
  >
>
type Temp_44 = Flatten<
  Temp_3 & {
    foo: string
    bar: number
  }
>
type Temp_5 = Flatten<
  Without<
    {
      foo: string
      bar: number
    },
    {}
  >
>
type Temp_66 = Flatten<Temp_5 & {}>
type res_77 = Temp_44 | Temp_66

const res_77_test1: res_77 = {
  foo: 'string',
  bar: 112
}
const res_77_test2: res_77 = {
  // bar: 112
}

// 没有 foo、bar
expectType<XORStruct>({})
// 同时拥有 foo、bar
expectType<XORStruct>({
  foo: 'linbudu',
  bar: 599
})
